﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;

namespace SharpDepend.Classes.Algorithms
{
    public enum BlurType
    {
        Both,
        HorizontalOnly,
        VerticalOnly,
    }

    /// <summary>
    /// Reference: http://www.cnblogs.com/dah/archive/2007/03/30/694527.html
    /// </summary>
    public class GaussianBlur // : StyleBase
    {
        private int _radius = 6;
        private int[] _kernel;
        private int _kernelSum;
        private int[,] _multable;
        private BlurType _blurType;

        private int[] b;
        private int[] g;
        private int[] r;

        private int[] b2;
        private int[] g2;
        private int[] r2;

        public GaussianBlur()
        {
            PreCalculateSomeStuff();
        }

        public GaussianBlur(int radius)
        {
            _radius = radius;
            PreCalculateSomeStuff();
        }

        private void PreCalculateSomeStuff()
        {
            int sz = _radius * 2 + 1;
            _kernel = new int[sz];
            _multable = new int[sz, 256];
            for (int i = 1; i <= _radius; i++)
            {
                int szi = _radius - i;
                int szj = _radius + i;
                _kernel[szj] = _kernel[szi] = (szi + 1) * (szi + 1);
                _kernelSum += (_kernel[szj] + _kernel[szi]);
                for (int j = 0; j < 256; j++)
                {
                    _multable[szj, j] = _multable[szi, j] = _kernel[szj] * j;
                }
            }
            _kernel[_radius] = (_radius + 1) * (_radius + 1);
            _kernelSum += _kernel[_radius];
            for (int j = 0; j < 256; j++)
            {
                _multable[_radius, j] = _kernel[_radius] * j;
            }
        }

        public void SetSize(int width, int height)
        {
            int pixelCount = width * height;
            b = new int[pixelCount];
            g = new int[pixelCount];
            r = new int[pixelCount];

            b2 = new int[pixelCount];
            g2 = new int[pixelCount];
            r2 = new int[pixelCount];
        }

        public void ProcessImage(int width, int height, int[] inputImage, int[] outputImage)
        {
            int width_ = width;
            int height_ = height;
            int[] origin_ = inputImage;
            int[] blurred_ = outputImage;

            int pixelCount = origin_.Length;
            int[] b_ = b;
            int[] g_ = g;
            int[] r_ = r;

            int[] b2_ = b2;
            int[] g2_ = g2;
            int[] r2_ = r2;

            int[] kernel_ = _kernel;
            int[,] multable_ = _multable;
            BlurType blurType_ = _blurType;

            int index = -1;
            for (int i = 0; i < height_; i++)
            {
                for (int j = 0; j < width_; j++)
                {
                    int color = origin_[++index];
                    b_[index] = (byte)(color >> 0);
                    g_[index] = (byte)(color >> 8);
                    r_[index] = (byte)(color >> 16);
                }
            }

            int bsum;
            int gsum;
            int rsum;
            int sum;
            int read;
            int start = 0;
            index = -1;
            int kernel_Length = kernel_.Length;
            if (blurType_ != BlurType.VerticalOnly)
            {
                for (int i = 0; i < height_; i++)
                {
                    for (int j = 0; j < width_; j++)
                    {
                        bsum = gsum = rsum = sum = 0;
                        read = ++index - _radius;

                        for (int z = 0; z < kernel_Length; z++)
                        {
                            if (read >= start && read < start + width_)
                            {
                                bsum += multable_[z, b_[read]];
                                gsum += multable_[z, g_[read]];
                                rsum += multable_[z, r_[read]];
                                sum += kernel_[z];
                            }
                            ++read;
                        }

                        b2_[index] = (bsum / sum);
                        g2_[index] = (gsum / sum);
                        r2_[index] = (rsum / sum);

                        if (blurType_ == BlurType.HorizontalOnly)
                        {
                            blurred_[i * width_ + j] =
                                255 << 24 |
                                (byte)(rsum / sum) << 16 |
                                (byte)(gsum / sum) << 8 |
                                (byte)(bsum / sum) << 0;
                        }
                    }
                    start += width_;
                }
            }
            if (blurType_ == BlurType.HorizontalOnly)
            {
                return;
            }

            int tempy;
            for (int i = 0; i < height_; i++)
            {
                int y = i - _radius;
                start = y * width_;
                for (int j = 0; j < width_; j++)
                {
                    bsum = gsum = rsum = sum = 0;
                    read = start + j;
                    tempy = y;
                    for (int z = 0; z < kernel_Length; z++)
                    {
                        if (tempy >= 0 && tempy < height_)
                        {
                            if (blurType_ == BlurType.VerticalOnly)
                            {
                                bsum += multable_[z, b_[read]];
                                gsum += multable_[z, g_[read]];
                                rsum += multable_[z, r_[read]];
                            }
                            else
                            {
                                bsum += multable_[z, b2_[read]];
                                gsum += multable_[z, g2_[read]];
                                rsum += multable_[z, r2_[read]];
                            }
                            sum += kernel_[z];
                        }
                        read += width_;
                        ++tempy;
                    }

                    blurred_[i * width_ + j] =
                        255 << 24 |
                        (byte)(rsum / sum) << 16 |
                        (byte)(gsum / sum) << 8 |
                        (byte)(bsum / sum) << 0;
                }
            }
        }

        public int Radius
        {
            get { return _radius; }
            set
            {
                if (value < 2)
                {
                    throw new InvalidOperationException("Radius must be greater then 1");
                }
                _radius = value;
                PreCalculateSomeStuff();
                //RaisePropertyChanged(this, new PropertyChangedEventArgs("Radius"));
            }
        }

        public BlurType BlurType
        {
            get { return _blurType; }
            set
            {
                _blurType = value;
                //RaisePropertyChanged(this, new PropertyChangedEventArgs("BlurType"));
            }
        }
    }
}
